﻿using System;
using System.Linq;
using GammaJul.ReSharper.GammaXaml.Psi;
using GammaJul.ReSharper.GammaXaml.Psi.Xaml;
using GammaJul.ReSharper.GammaXaml.Psi.Xml;
using JetBrains.Annotations;
using JetBrains.Application.Progress;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Feature.Services.Bulbs;
using JetBrains.ReSharper.Feature.Services.Xaml.Bulbs;
using JetBrains.ReSharper.Intentions.Extensibility;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.ExtensionsAPI.Tree;
using JetBrains.ReSharper.Psi.Parsing;
using JetBrains.ReSharper.Psi.Resolve;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.ReSharper.Psi.Util;
using JetBrains.ReSharper.Psi.Xaml;
using JetBrains.ReSharper.Psi.Xaml.Impl;
using JetBrains.ReSharper.Psi.Xaml.Impl.Resolve;
using JetBrains.ReSharper.Psi.Xaml.Impl.Util;
using JetBrains.ReSharper.Psi.Xaml.Tree;
using JetBrains.ReSharper.Psi.Xaml.Tree.MarkupExtensions;
using JetBrains.ReSharper.Psi.Xml.Parsing;
using JetBrains.ReSharper.Psi.Xml.Tree;
using JetBrains.Text;
using JetBrains.TextControl;
using JetBrains.Util;

namespace GammaJul.ReSharper.GammaXaml.ContextActions.Xaml {

	/// <summary>
	/// Converts a <c>{TemplateBinding}</c> to a <c>{Binding RelativeSource={x:Static RelativeSource.TemplatedParent}}</c>.
	/// </summary>
	[ContextAction(Name = "Convert to Binding", Group = "XAML", Description = "Converts a TemplateBinding to a Binding with RelativeSource TemplatedParent.")]
	public class TemplateBindingToBindingAction : ContextActionBase {
		private readonly XamlContextActionDataProvider _provider;

		public override string Text {
			get { return "Convert 'TemplateBinding' to 'Binding'"; }
		}

		/// <summary>
		/// Gets an optimized relative source string depending whether we're in WPF or Silverlight.
		/// </summary>
		/// <returns>An optimized relative source string.</returns>
		[NotNull]
		private string GetRelativeSourceString() {
			return SilverlightUtil.GetSilverlightness(_provider.PsiModule) == Silverlightness.NO_SILVERLIGHT
				? "{x:Static RelativeSource.TemplatedParent}"
				: "{RelativeSource TemplatedParent}";
		}

		protected override Action<ITextControl> ExecutePsiTransaction(ISolution solution, IProgressIndicator progress) {
			var markup = _provider.FindNodeAtCaret<IMarkup>();
			if (markup != null)
				ReplaceBindingMarkup(markup);

			else {
				var element = _provider.FindNodeAtCaret<IXamlObjectElement>();
				if (element != null)
					ReplaceBindingTag(element);
			}

			return null;
		}

		private void ReplaceBindingMarkup([NotNull] IMarkup markup) {
			XamlElementFactory elementFactory = XamlElementFactory.GetElementFactory(markup);
			XmlTokenTypes xmlTokenTypes = XmlTokenTypes.GetInstance<XamlLanguage>();

			// replace "TemplateBinding" name node with "Binding"
			IXamlIdentifier nameNode = markup.NameNode;
			if (nameNode != null)
				nameNode.ReplaceBy(elementFactory.CreateIdentifier("Binding", xmlTokenTypes));

			var attributeList = markup.Value as IAttributeListMarkupValue;

			// we don't have any attribute list, meaning we have a markup that is "{TemplateBinding}" (whitespaces aren't considered)
			if (attributeList == null) {
				IMarkup fakeMarkup = elementFactory.CreateMarkup("{Binding RelativeSource=" + GetRelativeSourceString() + "}");

				// markupValue is a newly created IAttributeListMarkupValue containing only RelativeSource: copy it without modification
				IMarkupValue markupValue = ModificationUtil.AddChildBefore(markup, markup.RBrace, fakeMarkup.Value);
				if (!markupValue.PrevSibling.IsWhitespaceToken())
					ModificationUtil.AddChildBefore(markupValue, xmlTokenTypes.SPACE.Create(new StringBuffer(" ")));
			}

			// we have an attribute list already
			else {

				// replace Property attribute with Path
				IMarkupAttribute propertyAttribute = markup.GetAttribute("Property");
				if (propertyAttribute != null) {
					IXamlIdentifier propertyNameNode = propertyAttribute.NameNode;
					if (propertyNameNode != null)
						propertyNameNode.ReplaceBy(elementFactory.CreateIdentifier("Path", xmlTokenTypes));
				}

				IMarkup fakeMarkup = elementFactory.CreateMarkup("{Binding Dummy, RelativeSource=" + GetRelativeSourceString() + "}");
				var fakeAttributeList = (IAttributeListMarkupValue) fakeMarkup.Value;
				Assertion.Assert(fakeAttributeList != null, "fakeAttributeList != null");

				// copy the comma, whitespace and RelativeSource attribute
				IMarkupAttribute dummyAttribute = fakeAttributeList.Attributes.First();
				var range = new TreeRange(dummyAttribute.NextSibling, fakeAttributeList.LastChild);
				ModificationUtil.AddChildRangeBefore(attributeList, null, range);
			}

			_provider.PsiServices.MarkAsDirty(_provider.SourceFile);
		}

		private void ReplaceBindingTag([NotNull] IXmlTag tag) {
			XamlElementFactory elementFactory = XamlElementFactory.GetElementFactory(tag);
			XmlTokenTypes xmlTokenTypes = XmlTokenTypes.GetInstance<XamlLanguage>();

			// replace "TemplateBinding" name node with "Binding"
			tag.GetNameNode().ReplaceBy(elementFactory.CreateIdentifier("Binding", xmlTokenTypes));

			// replace Property attribute with Path
			IXmlAttribute propertyAttribute = tag.GetAttribute("Property");
			if (propertyAttribute != null)
				propertyAttribute.NameNode.ReplaceBy(elementFactory.CreateIdentifier("Path", xmlTokenTypes));

			// adds a RelativeSource attribute
			IXmlAttribute attribute = elementFactory.CreateAttribute(
				"RelativeSource=\"" + GetRelativeSourceString() + "\"",
				_provider.PsiServices,
				_provider.PsiModule,
				true);
			tag.AddAttributeBefore(attribute, null);

			_provider.PsiServices.MarkAsDirty(_provider.SourceFile);
		}

		public override bool IsAvailable(IUserDataHolder cache) {

			// {TemplateBinding}
			var markup = _provider.FindNodeAtCaret<IMarkup>();
			if (markup != null)
				return markup.Name == XamlKeywords.TEMPLATE_BINDING;

			// <TemplateBinding> and <TemplateBindingExtension>
			var xamlObject = _provider.FindNodeAtCaret<IXamlObjectElement>();
			if (xamlObject != null) {
				IReference reference = xamlObject.TypeReference;
				if (reference != null) {
					var element = reference.Resolve().DeclaredElement as ITypeElement;
					return element != null && Equals(element, XamlTypeCache.GetCache(xamlObject).TemplateBindingExtension().GetTypeElement());
				}
			}

			return false;
		}

		public TemplateBindingToBindingAction([NotNull] XamlContextActionDataProvider provider) {
			if (provider == null)
				throw new ArgumentNullException("provider");
			_provider = provider;
		}
	}

}